# -*- coding: iso-8859-1 -*-
#
#    This is part of Malvinas Argentinas Messenger
#
#    Malvinas Argentinas Messenger is free software; you can redistribute
#    it and/or modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; either version 2 of the
#    License, or (at your option) any later version.
#
#    Malvinas Argentinas Messenger is distributed in the hope that it will be
#    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Malvinas Argentinas Messenger; if not, write to the Free
#    Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
#    MA  02110-1301  USA

# This would not have been completed without emesene p2p code.
# Main stuff taken from some old version of it, and modified for Malvinas
# Argentinas Messenger.

import sys, struct, string
import random, time
import msnlib, msnp2p

class dpManager:
	def __init__(self):
		self.dp = {}

	def delSession(self, email):
		if self.hasSessionWith(email):
			del self.dp[email]

	def listSessions(self):
		msnlib.debug('--------------')
		msnlib.debug('DP sessions:')
		for e in self.dp:			
			msnlib.debug(e + '\n')

	def addSession(self, email, mam, tid, message, nd):
		self.dp[email] = Base(mam, tid, message, nd)

	def hasSessionWith(self, email):
		if self.dp.has_key(email):
			return 1
		else:
			return 0
	
	def process(self, mam, tid, message, nd):
		msnlib.debug('---------------------------------------')
		msnlib.debug('Got DP msg')
		tid_copy = tid
		receiver = tid_copy.split(' ')[0]
		# is new invitation. create new session.
		if not self.hasSessionWith(receiver):
			self.addSession(receiver, mam, tid, message, nd)
		try:
			self.dp[receiver].process(tid, message)
		except "", e:
			for i in mam.m.sb_fds:
				mam.ui.padArray.addstr('EVENTS', '\n' + str(e))
				mam.ui.padArray.addstr('EVENTS', '\n' + str(i.emails), mam.ui.COLORS.BLUE_R)
				mam.ui.padArray.addstr('EVENTS', ' ' + str(i.state), mam.ui.COLORS.BLUE_R)
				mam.ui.padArray.addstr('EVENTS', ' ' + str(i.block), mam.ui.COLORS.BLUE_R)
				mam.ui.padArray.addstr('EVENTS', '\nDP error. Please, let know about this one.', mam.ui.COLORS.RED_R)
				msnlib.debug(str(i.emails)+'\tDP ERROR P2P\n')
				mam.ui.padArray.refresh('EVENTS')
		msnlib.debug('---------------------------------------')

class Base:
	def __init__(self, mam, tid, message, nd):
		self.mam = mam			# The system
		self.tid = tid			# The MSG dudeam@hotmail.com D... line
		self.message = message	# The following lines including the binary str
		self.nd = nd		
		self.receiver = ''		# The one who wants our pic
		self.identifier = self.generateRandomNumber(50000)
		self.baseID = self.identifier
		# msg and binary header		
		self.params = ''
		self.header = ''
		# some info from invitation content
		self.Via = ''
		self.CallID = ''
		self.SessionID = ''
		# last binary header sent
		self.myLastHeader = msnp2p.BinHeader()

	def suicide(self):
		self.mam.dpManager.delSession(self.receiver)

	def getParameters(self, s):
		'''parse a message and create and return a dict with the values'''
		d = {}
		try:
			header = s.split('\r\n\r\n')[1][:48]
			d['header'] = header
		except:
			pass        
		for i in s.split('\r\n'):
			l = i.split(': ')
			if len(l) == 2:
				d[l[0]] = l[1]
		return d

	def extractViaBranch(self):
		"""try to extract the via id from the invite message return empty string otherwise """
		try:
			return self.message.split("Via: MSNSLP/1.0/TLP ;branch={")[1].split("}")[0]
		except:
			return ""
	
	def extractCallID(self):
		"""try to extract the callID from the invite message return empty string otherwise """
		try:
			return self.message.split("Call-ID: {")[1].split("}")[0]
		except:
			return ""

	def extractSessionID(self):
		"""try to extract the sessionID from the invite message return an empty string otherwise"""
		try:
			return self.message.split("SessionID: ")[1].split("\r\n")[0]
		except:
			return ""

	def nextBaseID(self):
		if self.baseID == self.identifier:
			self.baseID -= 3
		else:
			self.baseID += 1
			if self.baseID == self.identifier:
				self.baseID += 1

	def generateRandomNumber(self, min = 0, max = 2147483647):
		return random.randint(min, max)

	def is200OKAck(self, header, message):
		if (self.myLastHeader.getTotalDataSize() == \
		header.getTotalDataSize()) and \
		(self.myLastHeader.getTotalDataSize() == \
		header.getAcknowledgedDataSize()) and (header.getFlag() == 2) and \
		(self.myLastHeader.getAcknowledgedIdentifier() == \
		header.getAcknowledgedUniqueID()) and \
		header.getAcknowledgedDataSize() <> 4 and \
		not (message.find('BYE MSNMSGR') > -1):
			return 1
		else:
			return 0

	def isNewInvite(self, message):
		if (message.find('INVITE MSNMSGR:') != -1) and \
		(message.find('Content-Type: application/x-msnmsgr-sessionreqbody') != -1) and\
		(message.find('AppID: 1') != -1):
			return 1
		else:
			return 0

	def isDataPrepAck(self, header):
		# Total data size could be 0 too. (Seen in WLM)
		if (header.getTotalDataSize() in [0, 4]) and \
		(header.getAcknowledgedDataSize() == 4) and \
		(header.getFlag() == 2) and \
		(header.getAcknowledgedIdentifier() == \
		self.myLastHeader.getIdentifier() ) and \
		(header.getAcknowledgedUniqueID() == \
		self.myLastHeader.getAcknowledgedIdentifier()):
			return 1
		else:
			return 0

	def isByeMessage(self, header, message):
		if (message.find('BYE MSNMSGR') > -1) and \
		(message.split("Call-ID: {" )[1].split("}")[0] == self.CallID):
			return 1
		else:
			return 0

	# TODO: Handle possible closing of socket before sending the messages?	
	def process(self, tid, message):
		self.tid = tid			# The MSG dudeam@hotmail.com D... line
		self.message = message	# The following lines including the binary str

		# Build the binary header received
		self.params = self.getParameters(self.message)
		self.header = msnp2p.BinHeader(self.params['header'])
		tid_copy = self.tid
		#self.receiver = tid_copy.split(' ')[0]
		
		# debugging
		msnlib.debug('* Header info:\n' + self.header.printFields())
		msnlib.debug('* Bin header:\n')
		for i in self.header:
			msnlib.debug(str(i) + ' ', 1)

		if self.isNewInvite(self.message):
			msnlib.debug('* MSG is new DP invitation\n')
			self.receiver = tid_copy.split(' ')[0]
			# Set some content parsing
			self.SessionID = self.extractSessionID()
			self.Via = self.extractViaBranch()
			self.CallID = self.extractCallID()
			self.sendACK(self.header, self.params)
			self.send200OK()
			self.m200ok = 0
		# TODO: Change this m200ok flag.
		# MSMSN and WLM does not wait for 200OK to send 200OKAck though. 
		# CHECK
		#elif self.is200OKAck(self.header, self.message) and self.m200ok == 0:
		#	msnlib.debug('Got 200OK ACK\n')
			self.sendDataPreparation()
			self.m200ok = 1
		elif self.isDataPrepAck(self.header):
			msnlib.debug('Got Data Prep ACK\n')
			self.sendData()		
		elif self.isByeMessage(self.header, self.message):
			msnlib.debug('Got Bye MSG\n')
			self.sendByeAck(self.header.getTotalDataSize(), \
			self.header.getIdentifier(), \
			self.header.getAcknowledgedIdentifier())
			self.suicide()
	
	def sendACK(self, bin, params):
		# binary header
		header = msnp2p.BinHeader()
		header.setIdentifier(self.baseID)
		self.nextBaseID()
		header.setTotalDataSize(bin.getTotalDataSize())
		header.setFlag(int(0x2))
		header.setAcknowledgedIdentifier(bin.getIdentifier())
		header.setAcknowledgedUniqueID(bin.getAcknowledgedIdentifier())
		header.setAcknowledgedDataSize(bin.getTotalDataSize())
		ack = header.getData()
		# we save last binary header sent
		self.myLastHeader = header
		# msg header
		msgHeader = "MIME-Version: 1.0\r\n"
		msgHeader += "Content-Type: application/x-msnmsgrp2p\r\n"
		msgHeader += "P2P-Dest: "+ self.receiver + "\r\n\r\n"	
		# send msg
		tid = self.nd.get_tid()			
		self.nd.fd.send('MSG ' + str(tid) + ' D ' + \
		str(len(msgHeader)+52) + '\r\n' + msgHeader + ack + \
		'\0\0\0\0')
		# debugging
		msnlib.debug('# Invitation Ack sent:\n' + header.printFields())
		msnlib.debug('# Bin Inv Ack sent:\n')
		for i in header:
			msnlib.debug(str(i) + ' ', 1)
		msnlib.debug('\n')
	
	def sendByeAck(self, sizeField, HisBaseIdentifier, HisBaseID):
		# msg header
		msgHeader = "MIME-Version: 1.0\r\n"
		msgHeader += "Content-Type: application/x-msnmsgrp2p\r\n"
		msgHeader += "P2P-Dest: "+ self.receiver + "\r\n\r\n"	
		# binary header
		header = msnp2p.BinHeader()
		header.setSessionID(0)
		header.setIdentifier(self.identifier + 1)
		self.nextBaseID()
		#header.setFlag(0x40)
		header.setFlag(0x20)
		header.setTotalDataSize(sizeField)
		header.setMessageLength(0)
		header.setAcknowledgedIdentifier(HisBaseIdentifier)
		header.setAcknowledgedDataSize(sizeField)
		header.setAcknowledgedUniqueID(HisBaseID)
		self.myLastHeader = header
		# debugging
		msnlib.debug('BYE ACK sent:\n' + header.printFields())
		tid = self.nd.get_tid()
		self.nd.fd.send('MSG ' + str(tid) + ' D ' + \
		str(len(msgHeader)+52) + '\r\n' + msgHeader + \
		header.getData() + '\0\0\0\0')

	def send200OK(self):	
		# header
		msgHeader = "MIME-Version: 1.0\r\n"
		msgHeader += "Content-Type: application/x-msnmsgrp2p\r\n"
		msgHeader += "P2P-Dest: "+ self.receiver + "\r\n\r\n"
		# body
		s = 'MSNSLP/1.0 200 OK\r\n'
		s += 'To: <msnmsgr:' + self.receiver + '>\r\n'
		s += 'From: <msnmsgr:' + self.mam.m.email + '>\r\n'
		s += 'Via: MSNSLP/1.0/TLP ;branch={' + self.Via + '}\r\n'
		s += 'CSeq: 1 \r\n'
		s += 'Call-ID: {' + self.CallID + '}\r\n'
		s += 'Max-Forwards: 0\r\n'
		s += 'Content-Type: application/x-msnmsgr-sessionreqbody\r\n'
		content = 'SessionID: ' + str(self.SessionID) + '\r\n\r\n\0'
		s += 'Content-Length: ' + str(len(content)) + '\r\n\r\n'
		# binary Header	
		header = msnp2p.BinHeader()
		header.setIdentifier(self.baseID) 
		self.nextBaseID()
		s += content	
		header.setTotalDataSize(len(s))
		header.setMessageLength(len(s))	
		header.setAcknowledgedIdentifier(self.generateRandomNumber())
		# we save last binary header sent
		self.myLastHeader = header
		# send the msg
		tid = self.nd.get_tid()
		self.nd.fd.send('MSG ' + str(tid) + ' D ' + \
		str(len(msgHeader) + len(s) + 52) + '\r\n' + msgHeader + \
		header.getData() + s + '\0\0\0\0')
		# debugging
		msnlib.debug('200 OK sent:\n' + header.printFields())
		msnlib.debug('BIN 200 OK:\n')
		for i in header:
			msnlib.debug(str(i) + ' ', 1)

	def sendDataPreparation(self):
		# header
		msgHeader = "MIME-Version: 1.0\r\n"
		msgHeader += "Content-Type: application/x-msnmsgrp2p\r\n"
		msgHeader += "P2P-Dest: "+ self.receiver + "\r\n\r\n"
		# binary header
		header = msnp2p.BinHeader()
		header.setSessionID(int(self.SessionID))
		header.setIdentifier(int(self.baseID))
		self.nextBaseID()
		header.setTotalDataSize(4)
		header.setMessageLength(4)
		header.setAcknowledgedIdentifier(self.generateRandomNumber())
		# save last binary header sent
		self.myLastHeader = header
		# debug
		msnlib.debug('\nData Prep sent:\n' + header.printFields())
		# send msg
		tid = self.nd.get_tid()
		self.nd.fd.send('MSG ' + str(tid) + ' D ' + \
		str(len(msgHeader)+52+4) + '\r\n' + msgHeader + \
		header.getData() + '\0\0\0\0' + '\0\0\0\1')

	def sendData(self):
		msnlib.debug('Starting sending data\n')
		# header
		msgHeader = "MIME-Version: 1.0\r\n"
		msgHeader += "Content-Type: application/x-msnmsgrp2p\r\n"
		msgHeader += "P2P-Dest: " + self.receiver + "\r\n\r\n"
		# pic data
		self.picture = Picture(sys.path[0] + "/.picture.tmp", 1)
		if not self.picture or self.picture.getSize() == 0:
			raise Exception( 'picture size 0 aborting' )

		moreData = True
		self.picture.setPointer(0)		
		# binary header
		while moreData:
			header = msnp2p.BinHeader()
			header.setSessionID(int(self.SessionID))
			header.setIdentifier(int(self.baseID))
			header.setDataOffset(self.picture.getPointer())
			header.setTotalDataSize(self.picture.getSize())
			header.setFlag(int(0x20))
			header.setAcknowledgedIdentifier(self.generateRandomNumber())
			if (self.picture.getSize() - self.picture.getPointer()) > 1202:
				msnlib.debug('Chunk size to send: ' + str(self.picture.getSize() - self.picture.getPointer()))
				length = 1202
				header.setMessageLength(length)
				chunk = self.picture.getData()[self.picture.getPointer():self.picture.getPointer()+ 1202]
				self.picture.setPointer(self.picture.getPointer() + 1202)
			else:
				length = self.picture.getSize() - self.picture.getPointer()
				msnlib.debug('Chunk size to send: ' + str(length))
				chunk = self.picture.getData()[self.picture.getPointer():]
				header.setMessageLength(length)
				self.picture.setPointer(self.picture.getSize())
				self.nextBaseID()
				moreData = False
			# save last header sent
			self.myLastHeader = header
			# debugging
			msnlib.debug('Data chunk sent:\n' + header.printFields())
			# send chunk
			tid = self.nd.get_tid()
			self.nd.fd.send('MSG ' + str(tid) + ' D ' + \
			str(len(msgHeader)+52+length) + '\r\n' + msgHeader + \
			header.getData() + chunk + '\0\0\0\1')

class Picture:
	'''a class that contains data about our display picture'''
	def __init__(self, path, action):
		'''Contructor'''
		self.path = path
		# if its open
		if action == 1:
			f = file( path, 'r' )
			if f:
				self.data = f.read()
				f.close()
			else:
				self.data = ''
	
		self.size = len(self.data)		
		# point to the actual point where the data is being readed
		self.pointer = 0
		msnlib.debug('Pic created. Its size: %s. Its pointer: %s' % (self.size, self.pointer))

	def getData(self):
		return self.data

	def getPointer(self):
		return self.pointer

	def setPointer(self, position):
		self.pointer = position

	def getSize(self):
		return self.size
